/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package poker.game;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * A TexasHand is a hand in the game of Texas Hold 'Em. There are two very
 * important things a TexasHand must take into account.
 * 
 * (1) A TexasHand can be from 2 - 7 cards. This is because at each step of the
 *     game, the number of cards grows. Evaluations must still apply.
 * (2) A TexasHand must be able to find the best hand in a 7 card deck. Hand
 *     evaluation in poker is done on groups of five cards, so this is a bit
 *     different than an evaluation on a 5 card deck, e.g., You can have
 *     3 pairs, and must find the best 2 of those.
 *
 *
 * @author Benjamin L. Brodie <blbrodie@gmail.com>
 */
public class TexasHand implements Hand {

    private List<Card> hand; //the list containing the elements of the hand, ie cards
    private List<Integer> handEval; //for evaluating hands against eachother
    private Map<Integer, Integer> freqMap; //stores the freq of each card
    private Map<Integer, Integer> suiteFreqMap;

    /**
     * Constructs a hand with the list of cards
     * @param cards a list of cards
     */
    public TexasHand(List<Card> cards) {
        
        if (cards.isEmpty()){
            hand = new ArrayList<Card>(cards);
            handEval = Arrays.asList(0,0,0,0);
        }
        else{
        this.hand = new ArrayList<Card>(cards); //safe copy
        freqMap = freqMapInit(); //init the frequency map
        suiteFreqMap = suiteFreqMapInit(); //init the suite freq map
        handEvaluator(); //set the evaluation list
        }

    }

    /*
     * Public methods
     *
     *
     */
    @Override
    public List<Card> getHand() {
        return new ArrayList<Card>(hand);
    }

    @Override
    public List<Integer> getEval() {
        return new ArrayList<Integer>(handEval);
    }

    @Override
    public Map<Integer, Integer> asMap() {
        return new HashMap<Integer, Integer>(freqMap);
    }

    @Override
    public int compareTo(Hand otherHand) {
        Iterator<Integer> it = handEval.listIterator();
        Iterator<Integer> ot = otherHand.getEval().listIterator();

        while (it.hasNext() && ot.hasNext()) {
            int itVal = it.next();
            int otVal = ot.next();

            if (itVal > otVal) {
                return 1;
            } else if (itVal < otVal) {
                return -1;
            }
        }

        return 0;

    }

     /**
     *
     * @return a string describing the hand in human terms, i.e.,
     * something like "2 of Hearts, 3 of Diamonds,..."
     *
     */
    @Override
    public String toString(){
        String s = "";


        //get the cards
        for (Iterator<Card> it = hand.listIterator(); it.hasNext(); ){
            s += it.next().toString();

            //add a comma
            if (it.hasNext()){
                s += ", ";
            }
        }

        s += ". ";

        //get the hand ranking
        for (HandTypes h : HandTypes.values()){
            if (h.getHandType() == handEval.get(0)){
                s += h.toString();
            }
        }

        return s;



    }

    /*
     *
     * End public methods
     *
     */
    private Map<Integer, Integer> suiteFreqMapInit() {
        Map<Integer, Integer> fmap = new HashMap<Integer, Integer>();
        Integer freq;

        for (Card c : hand) {
            freq = fmap.get(c.getSuite());
            fmap.put(c.getSuite(), (freq == null) ? 1 : freq + 1);
        }

        return fmap;
    }

    /*
     * create a map of each card to its frequency
     *
     */
    private Map<Integer, Integer> freqMapInit() {
        Map<Integer, Integer> fmap = new HashMap<Integer, Integer>();
        Integer freq;

        /*
         * for each card in hand, set the frequency to one if it is not yet in
         * the hand, or add one to the frequency
         */
        for (Card c : hand) {
            freq = fmap.get(c.getValue());
            fmap.put(c.getValue(), (freq == null) ? 1 : freq + 1);
        }

        return fmap;


    }

    /*
     *
     * HAND EVALUATION
     *
     *
     *
     */
    /*
     * Evaluates and assigns a value to the hand and rank to the hand. Value is
     * initial check to compare hands, then rank. I.e., a four of a kind is better
     * than two pair, but if both hands have two pair, their rank determines winner.
     */
    private void handEvaluator() {
        /*
         * Note that order is important here! Two pair is also two kind, etc.
         */


        if (royal_flush()) { //10
        } else if (straight_flush()) { //9
        } else if (four_kind()) { //8
        } else if (full_house()) { //7
        } else if (flush()) { //6
        } else if (straight()) { //5
        } else if (three_kind()) { //4
        } else if (two_pair()) { //3
        } else if (two_kind()) { //2
        } else {
            high_card(); //1
        }
    }

    private void high_card() {
        int highCard = 0;

        for (Card c : hand) {
            if (c.getValue() > highCard) {
                highCard = c.getValue();
            }
        }

        handEval = Arrays.asList(1, 0, 0, highCard);
    }


    /*
     * Checks for four of a kind
     */
    private boolean four_kind() {
        int handType = -1;
        int cardValue1 = -1;
        int cardValue2 = 0; //four of a kind doesnt have a second ranking
        int highCard = -1;

        if (freqMap.containsValue(4)) { //if its a 4 of a kind

            handType = HandTypes.FOUR_KIND.getHandType();

            //iterate over entry sets to find the card value
            for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {

                //find the cards
                if (entry.getValue() == 4 && entry.getKey() > cardValue1) {
                    cardValue1 = entry.getKey();
                } //find the high card
                else if (entry.getKey() > highCard) {
                    highCard = entry.getKey();
                }


            }

            //set the hand evaluation
            handEval = Arrays.asList(handType, cardValue1, cardValue2, highCard);

            return true;


        }

        return false;

    }

    /*
     * checks for full house
     *
     * note that a 7 card deck can have 2 sets of Trips or 2 sets of Doubles,
     * so need to find the best trips and doubles
     *
     */
    private boolean full_house() {
        int handType = -1;
        int cardValue1 = -1;
        int cardValue2 = -1;
        int highCard = 0; //no high card



        if (freqMap.containsValue(3) && freqMap.containsValue(2)) {
            handType = HandTypes.FULL_HOUSE.getHandType();

            //iterate over entry sets to find the card value
            for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {

                //find the cards
                //note that the && part is all that is needed to ensure
                // you find the greatest triple and greates pair
                if (entry.getValue() == 3 && entry.getKey() > cardValue1) {
                    cardValue1 = entry.getKey();
                }

                if (entry.getValue() == 2 && entry.getKey() > cardValue2) {
                    cardValue2 = entry.getKey();
                }




            }

            //set the hand evaluation
            handEval = Arrays.asList(handType, cardValue1, cardValue2, highCard);

            return true;

        }

        return false;



    }

    private boolean three_kind() {
        int handType = -1;
        int cardValue1 = -1;
        int cardValue2 = 0; //no second card value
        int highCard = -1;

        if (freqMap.containsValue(3)) { //if its a 4 of a kind

            handType = HandTypes.THREE_KIND.getHandType();

            //iterate over entry sets to find the card value
            for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {

                //find the cards
                if (entry.getValue() == 3 && entry.getKey() > cardValue1) {
                    cardValue1 = entry.getKey();
                } //find the high card
                else if (entry.getKey() > highCard) {
                    highCard = entry.getKey();
                }


            }

            //set the hand evaluation
            handEval = Arrays.asList(handType, cardValue1, cardValue2, highCard);

            return true;


        }

        return false;

    }

    private boolean two_pair() {
        int handType = -1;
        int cardValue1 = -1;
        int cardValue2 = -1;
        int highCard = -1;

        if (Collections.frequency(freqMap.values(), 2) == 2) {
            handType = HandTypes.TWO_PAIR.getHandType();

            //iterate over entry sets to find the card value
            for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
                int sortTemp;

                //find the cards, note that this works sort of like a stack
                //each higher value is pushed into cardValue2, but moves
                //to cardValue1 if >
                if (entry.getValue() == 2) {

                    if (entry.getKey() > cardValue2) {
                        cardValue2 = entry.getKey();
                    }


                    //keep them sorted
                    if (cardValue2 > cardValue1) {
                        sortTemp = cardValue2;
                        cardValue2 = cardValue1;
                        cardValue1 = sortTemp;
                    }
                } //find the high card
                else if (entry.getKey() > highCard) {
                    highCard = entry.getKey();
                }


            }



            //set the hand evaluation
            handEval = Arrays.asList(handType, cardValue1, cardValue2, highCard);

            return true;

        }

        return false;



    }

    private boolean two_kind() {
        int handType = -1;
        int cardValue1 = -1;
        int cardValue2 = 0; //no second card value
        int highCard = -1;

        if (freqMap.containsValue(2)) { //if its a 2 of a kind

            handType = HandTypes.TWO_KIND.getHandType();

            //iterate over entry sets to find the card value
            for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {

                //find the cards
                if (entry.getValue() == 2 && entry.getKey() > cardValue1) {
                    cardValue1 = entry.getKey();
                }

                //find the high card
                if (entry.getKey() > highCard) {
                    highCard = entry.getKey();
                }


            }

            //set the hand evaluation
            handEval = Arrays.asList(handType, cardValue1, cardValue2, highCard);

            return true;


        }

        return false;

    }

    private boolean royal_flush() {
        int handType = HandTypes.ROYAL_FLUSH.getHandType();
        int cardValue1 = 0; //no first value
        int cardValue2 = 0; //no second value
        int highCard = 0; // no high card

        if (suiteFreqMap.containsValue(5)) { //then its flush
            int royalCount = 0;
            int suite = -1;
            for (Map.Entry<Integer, Integer> entry : suiteFreqMap.entrySet()) {
                if (entry.getValue() == 5) {
                    suite = entry.getKey(); //set the suite to the flush value
                }
            }

            if (suite == -1) {
                throw new IllegalArgumentException("Problem in royal_flush() checking");
            }

            for (Card c : hand) {
                if (c.getSuite() == suite && c.getValue() >= 10) {
                    royalCount++;
                }
            }

            if (royalCount >= 5) {
                handEval = Arrays.asList(handType, cardValue1, cardValue2,
                        highCard);
                return true;
            }
        }

        return false;
    }

    private boolean straight_flush() {
        int handType = HandTypes.STRAIGHT_FLUSH.getHandType();
        int cardValue1 = -1;
        int cardValue2 = 0; //no second value
        int highCard = 0; // no high card

        if (suiteFreqMap.containsValue(5)) {
            int suite = -1;
            List<Integer> strList = new ArrayList<Integer>();

            for (Map.Entry<Integer, Integer> entry : suiteFreqMap.entrySet()) {
                if (entry.getValue() == 5) {
                    suite = entry.getKey();
                }
            }

            if (suite == -1) {
                throw new IllegalArgumentException(
                        "Problem in straight_flush() checking");
            }

            Collections.sort(hand); //needs to be sorted for the following oper

            /*
             *  adds successive elements of the same suite to the list. If
             *  the next element is of the suite but isn't successive, clear list.
             *
             *  At the end, if the list.size() == 5, then you have a straight flush.
             *  NOTICE: The hand was sorted before this!
             */
            for (Card c : hand) {

                //if its empty, just add the element if its of the right suite
                if (strList.isEmpty()) {
                    if (c.getSuite() == suite) {
                        strList.add(c.getValue());
                    }

                    //if there are elements in the list and the next card is the
                    // the correct suite, it must be the successive element, else
                    // delete the list and start over
                } else if (c.getSuite() == suite) {
                    if (c.getValue() == strList.get(strList.size() - 1) + 1) {
                        strList.add(c.getValue());
                    } else {
                        strList.clear();
                    }
                }

            }



            if (strList.size() > 4) {
                cardValue1 = strList.get(strList.size() - 1); //the highest value
                handEval = Arrays.asList(handType, cardValue1, cardValue2,
                        highCard);
                return true;
            } //TODO A 2 3 4 5 flush
        }

        return false;
    }

    private boolean flush() {
        int handType = HandTypes.FLUSH.getHandType();
        int cardValue1 = -1;
        int cardValue2 = 0; //no second value
        int highCard = 0; // no high card

        if (suiteFreqMap.containsValue(5)) {
            int suite = -1;

            for (Map.Entry<Integer, Integer> entry : suiteFreqMap.entrySet()) {
                if (entry.getValue() == 5) {
                    suite = entry.getKey();
                }
            }

            //the high card
            for (Card c : hand) {
                if (c.getSuite() == suite && c.getValue() > cardValue1) {
                    cardValue1 = c.getValue();
                }
            }

            handEval = Arrays.asList(handType, cardValue1, cardValue2, highCard);
            return true;
        }

        return false;
    }


    //TODO: update straight to include A 2 3 4 5
    private boolean straight() {
        int handType = HandTypes.STRAIGHT.getHandType();
        int cardValue1 = -1;
        int cardValue2 = 0; //no second value
        int highCard = 0; // no high card


        List<Integer> strList = new ArrayList<Integer>();

        Collections.sort(hand);

        for (Card c : hand) {
            if (strList.isEmpty()) {
                strList.add(c.getValue());
            } //if c.getValue() is 1+ the last ele in the list
            else if (c.getValue() == strList.get(strList.size() - 1) + 1) {
                strList.add(c.getValue());
            } else {
                strList.clear();
            }

        }

        if (strList.size() == 5) {
            cardValue1 = strList.get(strList.size() - 1);
            handEval = Arrays.asList(handType, cardValue1, cardValue2,
                    highCard);
            return true;
        } 
        //TODO Straight and Straigth Flush A,2,3,4,5
            
        else return false;
        }


    

    /*
     * ENUM
     */
    private static enum HandTypes {

        HIGH_CARD(1),
        TWO_KIND(2),
        TWO_PAIR(3),
        THREE_KIND(4),
        STRAIGHT(5),
        FLUSH(6),
        FULL_HOUSE(7),
        FOUR_KIND(8),
        STRAIGHT_FLUSH(9),
        ROYAL_FLUSH(10);
        private final int handType;

        private HandTypes(int typeValue) {
            this.handType = typeValue;
        }

        int getHandType() {
            return handType;
        }
    }

    public static void main(String[] args) {
        int number = 50;
        Random rand = new Random();
        Hand hand;
        List<Hand> special = new ArrayList<Hand>();
        List<Card> cards   = new ArrayList<Card>();

        for (int i = 0; i < number; i++) {
            Card c = null;

            while (cards.size() < 8){
                boolean contained;


                contained = false;
                c = new Card(rand.nextInt(13) + 2 , rand.nextInt(4) + 1);
                for (Card car : cards){
                    if (car.getValue() == c.getValue() && car.getSuite() == c.getSuite()){
                        contained = true;
                    }
                }

                if (!contained){
                    cards.add(c);
                }
            }

            //make a random hand
            hand = new TexasHand(cards);
            cards.clear();

//            System.out.println();
//            System.out.println("Hand " + i);
//            System.out.println(hand);
//            //System.out.println(hand.getHand());
//            System.out.println("freqMap: " + hand.asMap());
//            System.out.println("eval: " + hand.getEval());

            //specify the cuttoff for the hand value here
            if (hand.getEval().get(0) > 0){
                special.add(hand);
            }
        }

        for (Hand specHand : special){
            System.out.println();
            System.out.println("-------------------------------------------");
            System.out.println(specHand);
            System.out.println(specHand.getHand());
            System.out.println("freqMap: " + specHand.asMap());
            System.out.println("eval: " + specHand.getEval());

        }
    }
}
